<?php


namespace common\components;

/**
 * description of Tree
 *
 * @author FireLoong
 */
class Tree
{
    /**
     * @var string 主键
     */
    public $primary_key = 'id';
    /**
     * @var string 父键
     */
    public $parent_key = 'parent_id';
    /**
     * @var string 展开属性
     */
    public $expanded_key = 'expanded';
    /**
     * @var string 子节点
     */
    public $children_key = 'children';
    /**
     * @var string 叶子节点
     */
    public $leaf_key = 'leaf';
    /**
     * @var string 层次键
     */
    public $level_key = 'level';
    /**
     * @var string 左键
     */
    public $lft_key = 'lft';
    /**
     * @var string 右键
     */
    public $rgt_key = 'rgt';
    /**
     * @var bool 是否展开
     */
    public $expanded = false;
    /**
     * @var array 生成树型所需的二维数组
     */
    public $array = [];

    /**
     * @var array 层次数组
     */
    protected $level = [];
    /**
     * @var array 结果集
     */
    protected $result = [];

    /**
     * Tree constructor.
     * @param array $array
     * @param array $config
     */
    public function __construct($array = [], $config = [])
    {
        $this->array = $array;

        if (!empty($config)) {
            foreach ($config as $name => $value) {
                $this->$name = $value;
            }
        }
        return is_array($array);
    }

    /**
     * 获取当前位置数组
     * @param int $myId
     * @param array $newArr
     * @return array
     */
    public function getPos($myId, &$newArr = [])
    {
        foreach ($this->array as $item) {
            $newData[$item[$this->primary_key]] = $item;
        }
        if (!isset($newData[$myId])) {
            return [];
        }

        $newArr[] = $newData[$myId];
        $pid = $newData[$myId][$this->parent_key];
        if (isset($newData[$pid])) {
            $this->getPos($pid, $newArr);
        }

        $res = [];
        if (is_array($newArr)) {
            krsort($newArr);
            foreach ($newArr as $item) {
                $res[$item[$this->primary_key]] = $item;
            }
        }
        return $res;
    }

    /**
     * 获取子级数组
     * @param int $myId
     * @return array
     */
    public function getChildren($myId)
    {
        $newData = [];
        foreach ($this->array as $item) {
            if ($item[$this->parent_key] == $myId) {
                $newData[$item[$this->primary_key]] = $item;
            }
        }
        return $newData;
    }

    /**
     * 获取所有子级的 ID 数组
     * @param int $myId
     * @return array
     */
    public function getAllChildrenIds($myId)
    {
        $children = $this->getChildren($myId);
        $childrenTotal = empty($children) ? 0 : count($children);
        $ids = [];
        if ($childrenTotal > 0) {
            foreach ($children as $id => $child) {
                $ids[] = $id;
                $ids = array_merge($ids, $this->getAllChildrenIds($id));
            }
        }
        return $ids;
    }

    /**
     * 生成树型结构
     * @param int $id
     * @param int $maxLevel 最大层次，0：所有层次
     * @return array
     */
    public function makeTree($id = 0, $maxLevel = 0)
    {
        $dataset = $this->buildData($this->array);
        return $this->makeTreeCore($id, $dataset, $maxLevel, 'normal');
    }

    /**
     * 生成线性结构
     * @param int $id
     * @param int $maxLevel
     * @return array
     */
    public function makeTreeForHtml($id = 0, $maxLevel = 0)
    {
        $dataset = $this->buildData($this->array);
        return $this->makeTreeCore($id, $dataset, $maxLevel, 'linear');
    }

    /**
     * 格式化数据
     * @param array $data 数据
     * @return array
     */
    protected function buildData($data)
    {
        $r = [];
        if (is_array($data)) {
            foreach ($data as $datum) {
                $r[$datum[$this->parent_key] ?? 0][$datum[$this->primary_key]] = $datum;
            }
        }
        return $r;
    }

    /**
     * 生成树核心
     * @param int $index 索引值
     * @param array $data 数据
     * @param int $maxLevel 最大层次
     * @param string $type 类型，normal：树形结构，linear：线性结构
     * @return array
     */
    protected function makeTreeCore($index, $data, $maxLevel = 0, $type = 'linear')
    {
        if (!isset($data[$index])) {
            return [];
        }

        $r = [];
        foreach ($data[$index] as $id => $item) {
            if (!isset($item[$this->level_key])) {
                $parentLevel = $this->level[$item[$this->parent_key]] ?? -1;
                $this->level[$id] = $index == 0 ? 0 : $parentLevel + 1;
                $item[$this->level_key] = $this->level[$id];
            }
            if ($type == 'normal') {
                if ($maxLevel == 0 || $item[$this->level_key] < $maxLevel) {
                    if (isset($data[$id])) {
                        $item[$this->expanded_key] = $this->expanded;
                        $item[$this->children_key] = $this->makeTreeCore($id, $data, $maxLevel, $type);
                    } else {
                        $item[$this->leaf_key] = true;
                    }
                    $r[] = $item;
                }

            } elseif ($type == 'linear') {
                if ($maxLevel == 0 || $item[$this->level_key] < $maxLevel) {
                    $this->result[] = $item;
                    if (isset($data[$id])) {
                        $this->makeTreeCore($id, $data, $maxLevel, $type);
                    }
                }
                $r = $this->result;
            }
        }
        return $r;
    }

    /**
     * 重置左右键
     * @return array
     */
    public function resetLftRgt()
    {
        $linearData = $this->makeTreeForHtml();
        foreach ($linearData as $key => &$item) {
            $childrenCount = count($this->getAllChildrenIds($item['id'])) + 1;
            if ($item[$this->parent_key]) {
                $preDatum = $linearData[$key - 1];
                $pdl = $preDatum[$this->lft_key];
                $pdr = $preDatum[$this->rgt_key];
                $levelDiff = $preDatum[$this->level_key] - $item[$this->level_key];
                $item[$this->lft_key] = $pdr - $pdl > 1 ? $pdl + 1 : $pdr + 1 + $levelDiff;
                $item[$this->rgt_key] = $childrenCount == 1 ? $item[$this->lft_key] + 1 : $item[$this->lft_key] + $childrenCount * 2 - 1;
            } else {
                $item[$this->lft_key] = $key * 2;
                $item[$this->rgt_key] = $key * 2 + $childrenCount * 2 - 1;
            }
        }
        return $linearData;
    }
}
